// Software: GDI-JS
// Version: 2.3.7
// Author: Parveen Bhadoo
// Website: https://gdi.js.org
// add multiple serviceaccounts as {}, {}, {}, random account will be selected by each time app is opened.
const environment = 'production'; // This Variable Decides the environment of the app. 'production' or 'development' or 'local'
const serviceaccounts = [];
const randomserviceaccount = serviceaccounts[Math.floor(Math.random() * serviceaccounts.length)]; // DO NOT TOUCH THIS
const domains_for_dl = [''] // add multiple cloudflare addresses to balance the load on download/stream servers, eg. ['https://testing.fetchgoogleapi.workers.dev', 'https://testing2.fetchgoogleapi2.workers.dev']
const domain_for_dl = domains_for_dl[Math.floor(Math.random() * domains_for_dl.length)]; // DO NOT TOUCH THIS
const blocked_region = ['']; // add regional codes seperated by comma, eg. ['IN', 'US', 'PK']
const blocked_asn = []; // add ASN numbers from http://www.bgplookingglass.com/list-of-autonomous-system-numbers, eg. [16509, 12345]
const authConfig = {
"siteName": "Google Drive Index", // Website name
"client_id": "202264815644.apps.googleusercontent.com", // Client id from Google Cloud Console
"client_secret": "X4Z3ca8xfWDb1Voo-F9a7ZxJ", // Client Secret from Google Cloud Console
"refresh_token": "1//0ezc7kDR0JU3QCgYIARAAGA4SNwF-L9IrDsIWdTypYFxwzyQBchpnkvwih5QD6D1wjSmApca44dVJ8EAeI8Wr1FRbmhCmBVmEMqQ", // Authorize token
"service_account": false, // true if you're using Service Account instead of user account
"service_account_json": randomserviceaccount, // don't touch this one
"files_list_page_size": 100,
"search_result_list_page_size": 100,
"enable_cors_file_down": false,
"enable_password_file_verify": false, // support for .password file not working right now
"direct_link_protection": false, // protects direct links with Display UI
"disable_anonymous_download": false, // disables direct links without session
"file_link_expiry": 7, // expire file link in set number of days
"search_all_drives": true, // search all of your drives instead of current drive if set to true
"enable_login": false, // set to true if you want to add login system
"enable_signup": false, // set to true if you want to add signup system
"enable_social_login": false, // set to true if you want to add social login system
"google_client_id_for_login": "", // Google Client ID for Login
"google_client_secret_for_login": "", // Google Client Secret for Login
"redirect_domain": "", // Domain for login redirect eg. https://example.com
"login_database": "Local", // KV or Local
"login_days": 7, // days to keep logged in
"enable_ip_lock": false, // set to true if you want to lock user downloads to user IP
"single_session": false, // set to true if you want to allow only one session per user
"ip_changed_action": false, // set to true if you want to logout user if IP changed
"users_list": [{
"username": "admin",
"password": "admin",
},
{
"username": "admin1",
"password": "admin1",
}
],
"roots": [
{
"id":"https://drive.google.com/drive/folders/1Y33qt-wlvW1XE_UW9MD8cGfn7U3JpnEC",
"name": "00-MUST-HAVE",
"protect_file_link": false
},
]
};
const crypto_base_key = "3225f86e99e205347b4310e437253bfd" // Example 256 bit key used, generate your own.
const hmac_base_key = "4d1fbf294186b82d74fff2494c04012364200263d6a36123db0bd08d6be1423c" // Example 256 bit key used, generate your own.
const encrypt_iv = new Uint8Array([247, 254, 106, 195, 32, 148, 131, 244, 222, 133, 26, 182, 20, 138, 215, 81]); // Example 128 bit IV used, generate your own.
const uiConfig = {
"theme": "darkly", // switch between themes, default set to slate, select from https://gitlab.com/GoogleDriveIndex/Google-Drive-Index
"version": "2.3.7", // don't touch this one. get latest code using generator at https://bdi-generator.hashhackers.com
// If you're using Image then set to true, If you want text then set it to false
"logo_image": true, // true if you're using image link in next option.
"logo_height": "", // only if logo_image is true
"logo_width": "100px", // only if logo_image is true
"favicon": "https://cdn.jsdelivr.net/npm/@googledrive/index@2.2.3/images/favicon.ico",
// if logo is true then link otherwise just text for name
"logo_link_name": "https://cdn.jsdelivr.net/npm/@googledrive/index@2.2.3/images/bhadoo-cloud-logo-white.svg",
"login_image": "https://i.imgur.com/5fHELJr.png", // Login page logo image
"fixed_header": true, // If you want the footer to be flexible or fixed.
"header_padding": "80", // Value 80 for fixed header, Value 20 for flexible header. Required to be changed accordingly in some themes.
"nav_link_1": "Home", // change navigation link name
"nav_link_3": "Current Path", // change navigation link name
"nav_link_4": "Contact", // change navigation link name
"fixed_footer": false, // If you want the footer to be flexible or fixed.
"hide_footer": true, // hides the footer from site entirely.
"header_style_class": "navbar-dark bg-primary", // navbar-dark bg-primary || navbar-dark bg-dark || navbar-light bg-light
"footer_style_class": "bg-primary", // bg-primary || bg-dark || bg-light
"css_a_tag_color": "white", // Color Name or Hex Code eg. #ffffff
"css_p_tag_color": "white", // Color Name or Hex Code eg. #ffffff
"folder_text_color": "white", // Color Name or Hex Code eg. #ffffff
"loading_spinner_class": "text-light", // https://getbootstrap.com/docs/5.0/components/spinners/#colors
"search_button_class": "btn btn-danger", // https://getbootstrap.com/docs/5.0/components/buttons/#examples
"path_nav_alert_class": "alert alert-primary", // https://getbootstrap.com/docs/4.0/components/alerts/#examples
"file_view_alert_class": "alert alert-danger", // https://getbootstrap.com/docs/4.0/components/alerts/#examples
"file_count_alert_class": "alert alert-secondary", // https://getbootstrap.com/docs/4.0/components/alerts/#examples
"contact_link": "https://telegram.dog/Telegram", // Link to Contact Button on Menu
"copyright_year": "2050", // year of copyright, can be anything like 2015 - 2020 or just 2020
"company_name": "The Bay Index", // Name next to copyright
"company_link": "https://telegram.dog/Telegram", // link of copyright name
"credit": true, // Set this to true to give us credit
"display_size": true, // Set this to false to hide display file size
"display_time": false, // Set this to false to hide display modified time for folder and files
"display_download": true, // Set this to false to hide download icon for folder and files on main index
"disable_player": false, // Set this to true to hide audio and video players
"disable_video_download": false, // Remove Download, Copy Button on Videos
"allow_selecting_files": true, // Disable Selecting Files to Download in Bulk
"second_domain_for_dl": false, // If you want to display other URL for Downloading to protect your main domain.
"poster": "https://cdn.jsdelivr.net/npm/@googledrive/index@2.2.3/images/poster.jpg", // Video poster URL or see Readme to how to load from Drive
"audioposter": "https://cdn.jsdelivr.net/npm/@googledrive/index@2.2.3/images/music.jpg", // Video poster URL or see Readme to how to load from Drive
"jsdelivr_cdn_src": "https://cdn.jsdelivr.net/npm/@googledrive/index", // If Project is Forked, then enter your GitHub repo
"render_head_md": true, // Render Head.md
"render_readme_md": true, // Render Readme.md
"unauthorized_owner_link": "https://telegram.dog/Telegram", // Unauthorized Error Page Link to Owner
"unauthorized_owner_email": "abuse@telegram.org", // Unauthorized Error Page Owner Email
"downloaddomain": domain_for_dl, // Ignore this and set domains at top of this page after service accounts.
"show_logout_button": authConfig.enable_login ? true : false, // set to true if you want to add logout button
};
const player_config = {
"player": "videojs", // videojs || plyr || dplayer || jwplayer
"videojs_version": "8.3.0", // Change videojs version in future when needed.
"plyr_io_version": "3.7.8",
"jwplayer_version": "8.16.2"
}
// DON'T TOUCH BELOW THIS UNLESS YOU KNOW WHAT YOU'RE DOING
var gds = [];
const drive_list = authConfig.roots.map(it => it.id)
let app_js_file
if (environment === 'production') {
app_js_file = uiConfig.jsdelivr_cdn_src + '@' + uiConfig.version + '/src/app.min.js'
} else if (environment === 'development') {
app_js_file = '/app.js'
} else if (environment === 'local') {
app_js_file = 'http://127.0.0.1:5500/src/app.js'
}
function html(current_drive_order = 0, model = {}) {
return `
${authConfig.siteName}
`;
};
const homepage = `
${authConfig.siteName}
`
const login_html = `
Sign in - ${authConfig.siteName}
${authConfig.enable_social_login ? `
` : ''}
© ${uiConfig.company_name}
`
const signup_html = `
Sign UP - ${authConfig.siteName}
${authConfig.siteName}
${authConfig.enable_social_login ? `
` : ''}
© ${uiConfig.company_name}
`
const not_found = `
Error 404 (Not Found)!!1
404. That’s an error.
`
const asn_blocked = `
Access Denied
`
const directlink = `
Direct Link - Access Denied
`
const SearchFunction = {
formatSearchKeyword: function(keyword) {
let nothing = "";
let space = " ";
if (!keyword) return nothing;
return keyword.replace(/(!=)|['"=<>/\\:]/g, nothing)
.replace(/[,,|(){}]/g, space)
.trim()
}
};
const DriveFixedTerms = new(class {
default_file_fields = 'parents,id,name,mimeType,modifiedTime,createdTime,fileExtension,size';
gd_root_type = {
user_drive: 0,
share_drive: 1
};
folder_mime_type = 'application/vnd.google-apps.folder';
})();
// Token Generation for Service Accounts
const JSONWebToken = {
header: {
alg: 'RS256',
typ: 'JWT'
},
importKey: async function(pemKey) {
var pemDER = this.textUtils.base64ToArrayBuffer(pemKey.split('\n').map(s => s.trim()).filter(l => l.length && !l.startsWith('---')).join(''));
return crypto.subtle.importKey('pkcs8', pemDER, {
name: 'RSASSA-PKCS1-v1_5',
hash: 'SHA-256'
}, false, ['sign']);
},
createSignature: async function(text, key) {
const textBuffer = this.textUtils.stringToArrayBuffer(text);
return crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, textBuffer)
},
generateGCPToken: async function(serviceAccount) {
const iat = parseInt(Date.now() / 1000);
var payload = {
"iss": serviceAccount.client_email,
"scope": "https://www.googleapis.com/auth/drive",
"aud": "https://oauth2.googleapis.com/token",
"exp": iat + 3600,
"iat": iat
};
const encPayload = btoa(JSON.stringify(payload));
const encHeader = btoa(JSON.stringify(this.header));
var key = await this.importKey(serviceAccount.private_key);
var signed = await this.createSignature(encHeader + "." + encPayload, key);
return encHeader + "." + encPayload + "." + this.textUtils.arrayBufferToBase64(signed).replace(/\//g, '_').replace(/\+/g, '-');
},
textUtils: {
base64ToArrayBuffer: function(base64) {
var binary_string = atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
},
stringToArrayBuffer: function(str) {
var len = str.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes.buffer;
},
arrayBufferToBase64: function(buffer) {
let binary = '';
let bytes = new Uint8Array(buffer);
let len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
}
};
// web crypto functions
async function encryptString(string, iv) {
const key = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(crypto_base_key),
"AES-CBC",
false,
["encrypt"]
);
const encodedId = new TextEncoder().encode(string);
const encryptedData = await crypto.subtle.encrypt({
name: "AES-CBC",
iv: encrypt_iv
},
key,
encodedId
);
const encryptedString = btoa(Array.from(new Uint8Array(encryptedData), (byte) => String.fromCharCode(byte)).join(""));
return encryptedString;
}
async function decryptString(encryptedString) {
const key = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(crypto_base_key),
"AES-CBC",
false,
["decrypt"]
);
const encryptedBytes = Uint8Array.from(atob(encryptedString), (char) => char.charCodeAt(0));
const decryptedData = await crypto.subtle.decrypt({
name: "AES-CBC",
iv: encrypt_iv
},
key,
encryptedBytes
);
const decryptedString = new TextDecoder().decode(decryptedData);
return decryptedString;
}
// Web Crypto Integrity Generate API
async function genIntegrity(data, key = hmac_base_key) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(data);
const hmacKey = await crypto.subtle.importKey(
'raw',
encoder.encode(key), {
name: 'HMAC',
hash: 'SHA-256'
},
false,
['sign']
);
const hmacBuffer = await crypto.subtle.sign('HMAC', hmacKey, dataBuffer);
// Convert the HMAC buffer to hexadecimal string
const hmacArray = Array.from(new Uint8Array(hmacBuffer));
const hmacHex = hmacArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
return hmacHex;
}
async function checkintegrity(text1, text2) {
return text1 === text2;
}
function login() {
return new Response(login_html, {
status: 401,
headers: {
'Content-Type': 'text/html; charset=utf-8'
}
});
}
// start handlerequest
async function handleRequest(request, event) {
const region = request.headers.get('cf-ipcountry');
const asn_servers = request.cf.asn;
const referer = request.headers.get("Referer");
var user_ip = request.headers.get("CF-Connecting-IP");
let url = new URL(request.url);
let path = url.pathname;
let hostname = url.hostname;
if (path == '/app.js') {
const js = await fetch('https://gitlab.com/GoogleDriveIndex/Google-Drive-Index/-/raw/dev/src/app.js', {
method: 'GET',
})
const data = await js.text()
return new Response(data, {
status: 200,
headers: {
'Content-Type': 'application/javascript; charset=utf-8',
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
}
});
}
if (path == '/logout') {
let response = new Response("", {});
response.headers.set('Set-Cookie', `session=; HttpOnly; Secure; SameSite=Lax;`);
response.headers.set("Refresh", "1; url=/?error=Logged Out");
return response;
}
if (path == '/findpath') {
const params = url.searchParams;
const id = params.get('id');
const view = params.get('view') || 'false';
return Response.redirect(url.protocol + hostname + '/0:findpath?id=' + id + '&view=' + view, 307);
}
if (authConfig.enable_login) {
const login_database = authConfig.login_database.toLowerCase();
//console.log("Login Enabled")
if (path == '/download.aspx' && !authConfig.disable_anonymous_download) {
console.log("Anonymous Download")
} else if (path == '/google_callback') {
// Extract the authorization code from the query parameters
const code = url.searchParams.get('code')
if (!code) {
return new Response('Missing authorization code.', {
status: 400
});
}
// Use the authorization code to obtain access token and ID token
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
code,
client_id: authConfig.google_client_id_for_login,
client_secret: authConfig.google_client_secret_for_login,
redirect_uri: authConfig.redirect_domain + '/google_callback',
grant_type: 'authorization_code',
}),
});
const data = await response.json();
console.log(JSON.stringify(data));
if (response.ok) {
const idToken = data.id_token;
const decodedIdToken = await decodeJwtToken(idToken);
const username = decodedIdToken.email;
let kv_key
let user_found = false;
// Check if user email exist in the list
if (login_database == 'kv') {
kv_key = await ENV.get(username);
if (kv_key == null) {
user_found = false;
} else {
user_found = true;
}
} else if (login_database == 'mongodb') {
// to be implemented later
} else { // local database
for (i = 0; i < authConfig.users_list.length; i++) {
if (authConfig.users_list[i].username == username) {
user_found = true;
console.log("User Found")
break;
}
}
}
if (!user_found) {
if (authConfig.enable_signup && login_database == 'kv') {
await ENV.put(username, Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15));
kv_key = await ENV.get(username);
if (kv_key == null) {
user_found = false;
} else {
user_found = true;
}
} else {
let response = new Response('Invalid User! Google Login', {});
response.headers.set('Set-Cookie', `session=; HttpOnly; Secure; SameSite=Lax;`);
response.headers.set("Refresh", "1; url=/?error=Invalid User");
return response;
}
}
const current_time = Date.now(); // this results in a timestamp of the number of milliseconds since epoch.
const session_time = current_time + 86400000 * authConfig.login_days;
const encryptedSession = `${await encryptString(username)}|${await encryptString(kv_key)}|${await encryptString(session_time.toString())}`;
if (authConfig.single_session) {
await ENV.put(username + '_session', encryptedSession);
}
if (authConfig.ip_changed_action && user_ip) {
await ENV.put(username + '_ip', user_ip);
}
// reload page with cookie
let response = new Response("", {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Set-Cookie': `session=${encryptedSession}; path=/; HttpOnly; Secure; SameSite=Lax`,
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
'Refresh': '0; url=/',
}
});
return response;
} else {
let response = new Response('Invalid Token!', {});
response.headers.set('Set-Cookie', `session=; HttpOnly; Secure; SameSite=Lax;`);
response.headers.set("Refresh", "1; url=/?error=Invalid Token");
return response;
}
} else if (authConfig.enable_login && request.method === 'POST' && path === '/login') {
console.log("POST Request for Login")
const formdata = await request.formData();
const username = formdata.get('username');
const password = formdata.get('password');
if (login_database == 'kv') {
const kv_key = await ENV.get(username);
if (kv_key == null) {
var user_found = false;
} else {
if (kv_key == password) {
var user_found = true;
} else {
var user_found = false;
}
}
} else if (login_database == 'mongodb') {
// to be implemented later
} else { // local database
for (i = 0; i < authConfig.users_list.length; i++) {
if (authConfig.users_list[i].username == username && authConfig.users_list[i].password == password) {
var user_found = true;
break;
}
}
}
if (!user_found) {
const jsonResponse = {
ok: false,
}
let response = new Response(JSON.stringify(jsonResponse), {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
}
});
return response;
}
if (user_found) {
const current_time = Date.now(); // this results in a timestamp of the number of milliseconds since epoch.
const session_time = current_time + 86400000 * authConfig.login_days;
const encryptedSession = `${await encryptString(username)}|${await encryptString(password)}|${await encryptString(session_time.toString())}`;
if (authConfig.single_session) {
await ENV.put(username + '_session', encryptedSession);
}
if (authConfig.ip_changed_action && user_ip) {
await ENV.put(username + '_ip', user_ip);
}
const jsonResponse = {
ok: true,
}
let response = new Response(JSON.stringify(jsonResponse), {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Set-Cookie': `session=${encryptedSession}; path=/; HttpOnly; Secure; SameSite=Lax`,
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
}
});
return response;
} else {
const jsonResponse = {
ok: false,
}
let response = new Response(JSON.stringify(jsonResponse), {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
}
});
return response;
}
} else if (path == '/signup' && authConfig.enable_signup) {
return new Response(signup_html, {
status: 200,
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
}
});
} else if (authConfig.enable_signup && request.method === 'POST' && path === '/signup_api') {
if (login_database == 'kv') {
const formdata = await request.formData();
const username = formdata.get('username');
const password = formdata.get('password');
if (username == null || password == null) {
const jsonResponse = {
ok: true,
error: "Username or Password is null"
}
let response = new Response(JSON.stringify(jsonResponse), {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Set-Cookie': `session=; path=/; HttpOnly; Secure; SameSite=Lax`,
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
}
});
return response;
} else if (username.length > 8 && password.length > 8) {
const checkKey = await ENV.get(username);
let jsonResponse;
if (checkKey != null) {
jsonResponse = {
ok: false,
error: "User Already Exists"
}
} else {
await ENV.put(username, password);
jsonResponse = {
ok: true,
error: "User Created"
}
}
let response = new Response(JSON.stringify(jsonResponse), {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Set-Cookie': `session=; path=/; HttpOnly; Secure; SameSite=Lax`,
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
}
});
return response;
} else {
const jsonResponse = {
ok: false,
error: "Username or Password length is less than 8 characters"
}
let response = new Response(JSON.stringify(jsonResponse), {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Set-Cookie': `session=; path=/; HttpOnly; Secure; SameSite=Lax`,
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
}
});
return response;
}
} else if (login_database == 'mongodb') {
// to be implemented later
} else {
return new Response("Signup is not supported with local database", {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
'Access-Control-Allow-Credentials': true, // Required for cookies, authorization headers with HTTPS
}
});
}
} else if (request.method === 'GET') {
//console.log("GET Request")
const cookie = request.headers.get('cookie');
if (cookie && cookie.includes('session=')) {
const session = cookie.split('session=').pop().split(';').shift().trim();
if (session == 'null' || session == '' || session == null) {
return login()
}
const username = await decryptString(session.split('|')[0]);
let kv_session
if (authConfig.single_session) {
kv_session = await ENV.get(username + '_session');
if (kv_session != session) {
let response = new Response('User Logged in Someplace Else!', {
headers: {
'Set-Cookie': `session=; HttpOnly; Secure; SameSite=Lax;`,
}
});
response.headers.set("Refresh", "1; url=/?error=User Logged in Someplace Else!");
return response;
}
}
if (authConfig.ip_changed_action && user_ip) {
const kv_ip = await ENV.get(username + '_ip');
if (kv_ip != user_ip) {
let response = new Response('IP Changed! Login Required', {
headers: {
'Set-Cookie': `session=; HttpOnly; Secure; SameSite=Lax;`,
}
});
response.headers.set("Refresh", "1; url=/?error=IP Changed! Login Required");
return response;
}
}
const session_time = await decryptString(session.split('|')[2]);
console.log("User: " + username + " | Session Time: " + session_time)
const current_time = Date.now(); // this results in a timestamp of the number of milliseconds since epoch.
if (Number(session_time) < current_time) {
let response = new Response('Session Expired!', {
headers: {
'Set-Cookie': `session=; HttpOnly; Secure; SameSite=Lax;`,
}
});
response.headers.set("Refresh", "1; url=/?error=Session Expired!");
return response;
}
if (login_database == 'kv') {
const kv_key = await ENV.get(username);
if (kv_key == null) {
var user_found = false;
} else {
if (kv_key) {
var user_found = true;
} else {
var user_found = false;
}
}
} else if (login_database == 'mongodb') {
// to be implemented later
} else { // local database
for (i = 0; i < authConfig.users_list.length; i++) {
if (authConfig.users_list[i].username == username) {
var user_found = true;
break;
}
}
}
if (user_found) {
console.log("User Found")
} else {
let response = new Response('Invalid User! Something Wrong', {});
response.headers.set('Set-Cookie', `session=; HttpOnly; Secure; SameSite=Lax;`);
response.headers.set("Refresh", "1; url=/?error=Invalid User");
return response;
}
} else {
return login()
}
}
}
if (gds.length === 0) {
for (let i = 0; i < authConfig.roots.length; i++) {
const gd = new googleDrive(authConfig, i);
await gd.init();
gds.push(gd)
}
let tasks = [];
gds.forEach(gd => {
tasks.push(gd.initRootType());
});
for (let task of tasks) {
await task;
}
}
let gd;
function redirectToIndexPage() {
return new Response('', {
status: 307,
headers: {
'Location': `${url.origin}/0:/`
}
});
}
if (region && blocked_region.includes(region.toUpperCase())) {
return new Response(asn_blocked, {
status: 403,
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
} else if (asn_servers && blocked_asn.includes(asn_servers)) {
return new Response(asn_blocked, {
headers: {
'content-type': 'text/html;charset=UTF-8'
},
status: 401
});
} else if (path == '/') {
return new Response(homepage, {
status: 200,
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
} else if (path == '/fallback') {
return new Response(html(0, {
is_search_page: false,
root_type: 1
}), {
status: 200,
headers: {
'Content-Type': 'text/html; charset=utf-8'
}
});
} else if (path == '/download.aspx') {
console.log("Download.aspx started");
const file = await decryptString(url.searchParams.get('file'));
console.log(file)
const expiry = await decryptString(url.searchParams.get('expiry'));
let integrity_result = false;
if (authConfig['enable_ip_lock'] && user_ip) {
const integrity = await genIntegrity(`${file}|${expiry}|${user_ip}`);
const mac = url.searchParams.get('mac');
integrity_result = await checkintegrity(mac, integrity);
} else {
const integrity = await genIntegrity(`${file}|${expiry}`);
const mac = url.searchParams.get('mac');
integrity_result = await checkintegrity(mac, integrity);
}
if (integrity_result) {
let range = request.headers.get('Range');
const inline = 'true' === url.searchParams.get('inline');
console.log(file, range)
return download(file, range, inline);
} else {
return new Response('Invalid Request!', {
status: 401,
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
}
}
if (authConfig['direct_link_protection']) {
if (referer == null) {
return new Response(directlink, {
headers: {
'content-type': 'text/html;charset=UTF-8'
},
status: 401
});
} else if (referer.includes(hostname)) {
console.log("Refer Detected");
} else {
return new Response(directlink, {
headers: {
'content-type': 'text/html;charset=UTF-8'
},
status: 401
});
}
}
const command_reg = /^\/(?\d+):(?[a-zA-Z0-9]+)(\/.*)?$/g;
const match = command_reg.exec(path);
if (match) {
const num = match.groups.num;
const order = Number(num);
if (order >= 0 && order < gds.length) {
gd = gds[order];
} else {
return redirectToIndexPage()
}
//for (const r = gd.basicAuthResponse(request); r;) return r;
const command = match.groups.command;
if (command === 'search') {
if (request.method === 'POST') {
return handleSearch(request, gd, user_ip);
} else {
const params = url.searchParams;
return new Response(html(gd.order, {
q: params.get("q").replace(/'/g, "").replace(/"/g, "") || '',
is_search_page: true,
root_type: gd.root_type
}), {
status: 200,
headers: {
'Content-Type': 'text/html; charset=utf-8'
}
});
}
} else if (command === 'id2path' && request.method === 'POST') {
return handleId2Path(request, gd)
} else if (command === 'fallback' && request.method === 'POST') {
const formdata = await request.json();
const id = await decryptString(formdata.id);
const type = formdata.type;
if (type && type == 'folder') {
const page_token = formdata.page_token || null;
const page_index = formdata.page_index || 0;
const details = await gd._list_gdrive_files(id, page_token, page_index);
for (const file of details.data.files) {
if (file.mimeType != 'application/vnd.google-apps.folder') {
file.link = await generateLink(file.id, user_ip);
}
file.driveId = await encryptString(file.driveId);
file.id = await encryptString(file.id);
}
const encryptedDetails = details;
return new Response(JSON.stringify(encryptedDetails), {});
}
const details = await gd.findItemById(id)
details.link = await generateLink(details.id, user_ip);
details.id = formdata.id;
details.parents[0] = null;
return new Response(JSON.stringify(details), {});
} else if (command === 'findpath' && request.method === 'GET') {
return findId2Path(gd, url)
}
}
const common_reg = /^\/\d+:\/.*$/g;
try {
if (!path.match(common_reg)) {
return redirectToIndexPage();
}
let split = path.split("/");
let order = Number(split[1].slice(0, -1));
if (order >= 0 && order < gds.length) {
gd = gds[order];
} else {
return redirectToIndexPage()
}
} catch (e) {
return redirectToIndexPage()
}
//path = path.replace(gd.url_path_prefix, '') || '/';
if (request.method == 'POST') {
return apiRequest(request, gd, user_ip);
}
let action = url.searchParams.get('a');
if (path.slice(-1) == '/' || action != null) {
return new Response(html(gd.order, {
root_type: gd.root_type
}), {
status: 200,
headers: {
'Content-Type': 'text/html; charset=utf-8'
}
});
} else {
/*if (path.split('/').pop().toLowerCase() == ".password") {
return new Response("", {
status: 404
});
}*/
console.log(path)
const file = await gd.get_single_file(path.slice(3));
console.log(file)
let range = request.headers.get('Range');
const inline = 'true' === url.searchParams.get('inline');
if (gd.root.protect_file_link && enable_login) return login();
return download(file.id, range, inline);
}
}
// end handlerequest
function enQuery(data) {
const ret = [];
for (let d in data) {
ret.push(encodeURIComponent(d) + '=' + encodeURIComponent(data[d]));
}
return ret.join('&');
}
async function getAccessToken() {
if (authConfig.expires == undefined || authConfig.expires < Date.now()) {
const obj = await fetchAccessToken();
if (obj.access_token != undefined) {
authConfig.accessToken = obj.access_token;
authConfig.expires = Date.now() + 3500 * 1000;
}
}
return authConfig.accessToken;
}
async function fetchAccessToken() {
console.log("fetchAccessToken");
const url = "https://www.googleapis.com/oauth2/v4/token";
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
var post_data;
if (authConfig.service_account && typeof authConfig.service_account_json != "undefined") {
const jwttoken = await JSONWebToken.generateGCPToken(authConfig.service_account_json);
post_data = {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: jwttoken,
};
} else {
post_data = {
client_id: authConfig.client_id,
client_secret: authConfig.client_secret,
refresh_token: authConfig.refresh_token,
grant_type: "refresh_token",
};
}
let requestOption = {
'method': 'POST',
'headers': headers,
'body': enQuery(post_data)
};
let response;
for (let i = 0; i < 3; i++) {
response = await fetch(url, requestOption);
if (response.ok) {
break;
}
await sleep(800 * (i + 1));
}
return await response.json();
}
async function sleep(ms) {
return new Promise(function(resolve, reject) {
let i = 0;
setTimeout(function() {
console.log('sleep' + ms);
i++;
if (i >= 2) reject(new Error('i>=2'));
else resolve(i);
}, ms);
})
}
async function generateLink(file_id, user_ip, iv) {
const encrypted_id = await encryptString(file_id, iv);
const expiry = Date.now() + 1000 * 60 * 60 * 24 * authConfig.file_link_expiry;
const encrypted_expiry = await encryptString(expiry.toString(), iv);
let url
if (authConfig['enable_ip_lock'] && user_ip) {
const encrypted_ip = await encryptString(user_ip, iv);
const integrity = await genIntegrity(`${file_id}|${expiry}|${user_ip}`);
url = `/download.aspx?file=${encodeURIComponent(encrypted_id)}&expiry=${encodeURIComponent(encrypted_expiry)}&ip=${encodeURIComponent(encrypted_ip)}&mac=${encodeURIComponent(integrity)}`;
} else {
const integrity = await genIntegrity(`${file_id}|${expiry}`);
url = `/download.aspx?file=${encodeURIComponent(encrypted_id)}&expiry=${encodeURIComponent(encrypted_expiry)}&mac=${encodeURIComponent(integrity)}`;
}
return url;
}
async function apiRequest(request, gd, user_ip) {
let url = new URL(request.url);
let path = url.pathname;
path = path.replace(gd.url_path_prefix, '') || '/';
console.log("handling apirequest: " + path);
let option = {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*'
}
}
if (path.slice(-1) == '/') {
let requestData = await request.json();
let list_result = await gd.request_list_of_files(
path,
requestData.page_token || null,
Number(requestData.page_index) || 0
);
if (authConfig['enable_password_file_verify']) {
let password = await gd.password(path);
// console.log("dir password", password);
if (password && password.replace("\n", "") !== form.get('password')) {
let html = `Y29kZWlzcHJvdGVjdGVk=0Xfi4icvJnclBCZy92dzNXYwJCI6ISZnF2czVWbiwSMwQDI6ISZk92YisHI6IicvJnclJyeYmFzZTY0aXNleGNsdWRlZA==`;
return new Response(html, option);
}
}
list_result.data.files = await Promise.all(list_result.data.files.map(async (file) => {
const {
driveId,
id,
mimeType,
...fileWithoutId
} = file;
const encryptedId = await encryptString(id);
const encryptedDriveId = await encryptString(driveId);
let link = null;
if (mimeType !== 'application/vnd.google-apps.folder') {
link = await generateLink(id, user_ip);
}
return {
...fileWithoutId,
id: encryptedId,
driveId: encryptedDriveId,
mimeType: mimeType,
link: link,
};
}));
const encryptedFiles = list_result;
const data = JSON.stringify(encryptedFiles)
return new Response(data, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json;charset=UTF-8'
}
});
} else {
let file_json = await gd.get_single_file(path);
const {
driveId,
id,
...fileWithoutId
} = file_json;
const encryptedId = await encryptString(id);
const encryptedDriveId = await encryptString(driveId);
const link = await generateLink(id, user_ip);
const encryptedFile = {
...fileWithoutId,
id: encryptedId,
driveId: encryptedDriveId,
link: link,
};
const encryptedFiles = encryptedFile;
const data = JSON.stringify(encryptedFiles)
return new Response(data, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json;charset=UTF-8'
}
});
}
}
// deal with search
async function handleSearch(request, gd, user_ip = '') {
const option = {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*'
}
};
const requestData = await request.json();
const q = requestData.q || '';
const pageToken = requestData.page_token || null;
const pageIndex = Number(requestData.page_index) || 0;
if (q == '') return new Response(JSON.stringify({
"nextPageToken": null,
"curPageIndex": 0,
"data": {
"files": []
}
}), option);
const searchResult = await gd.searchFilesinDrive(q, pageToken, pageIndex);
searchResult.data.files = await Promise.all(searchResult.data.files.map(async (file) => {
const {
driveId,
id,
...fileWithoutId
} = file;
const encryptedId = await encryptString(id);
const encryptedDriveId = await encryptString(driveId);
const link = await generateLink(id, user_ip);
return {
...fileWithoutId,
id: encryptedId,
driveId: encryptedDriveId,
link: link,
};
}));
return new Response(JSON.stringify(searchResult), option);
}
async function handleId2Path(request, gd) {
let url = new URL(request.url);
const option = {
status: 200,
headers: {
"content-type": "application/json",
"Access-Control-Allow-Origin": authConfig.cors_domain,
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
}
};
try {
const data = await request.json();
const id = await decryptString(data.id);
let [path, prefix] = await gd.findPathById(id);
let jsonpath = '{"path": "/' + prefix + ':' + path + '"}'
console.log(jsonpath)
return new Response(jsonpath || '', option);
} catch (error) {
console.log(error)
return new Response('{"message":"Request Failed or Path Not Found","error":"' + error + '"}', {
status: 500,
headers: {
"content-type": "application/json",
"Access-Control-Allow-Origin": authConfig.cors_domain,
"Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS",
}
});
}
}
async function findId2Path(gd, url) {
try {
let [path, prefix] = await gd.findPathById(url.searchParams.get('id'));
console.log(path, prefix)
if (!path) {
return new Response("Invalid URL");
} else if (url.searchParams.get('view') && url.searchParams.get('view') == 'true') {
//return new Response("https://" + url.hostname + "/" + prefix + ":" + path + "?a=view" || '');
return Response.redirect("https://" + url.hostname + "/" + prefix + ":" + path + "?a=view" || '', 302);
} else {
//return new Response("https://" + url.hostname + "/" + prefix + ":" + path + "?a=view" || '');
return Response.redirect("https://" + url.hostname + "/" + prefix + ":" + path || '', 302);
}
} catch (error) {
const encrypted_id = await encryptString(url.searchParams.get('id'), encrypt_iv)
return Response.redirect("https://" + url.hostname + "/fallback?id=" + encrypted_id || '', 302);
}
}
/*async function findItemById(gd, id) {
console.log(id)
const is_user_drive = this.root_type === DriveFixedTerms.gd_root_type.user_drive;
let url = `https://www.googleapis.com/drive/v3/files/${id}?fields=${DriveFixedTerms.default_file_fields}${is_user_drive ? '' : '&supportsAllDrives=true'}`;
let requestOption = await gd.requestOptions();
let res = await fetch(url, requestOption);
return await res.json();
}*/
// start of class googleDrive
class googleDrive {
constructor(authConfig, order) {
this.order = order;
this.root = authConfig.roots[order];
this.root.protect_file_link = this.root.protect_file_link || false;
this.url_path_prefix = `/${order}:`;
this.authConfig = authConfig;
this.paths = [];
this.files = [];
this.passwords = [];
this.paths["/"] = this.root['id'];
}
async init() {
await getAccessToken();
if (authConfig.user_drive_real_root_id) return;
const root_obj = await (gds[0] || this).findItemById('root');
if (root_obj && root_obj.id) {
authConfig.user_drive_real_root_id = root_obj.id
}
}
async initRootType() {
const root_id = this.root['id'];
const types = DriveFixedTerms.gd_root_type;
if (root_id === 'root' || root_id === authConfig.user_drive_real_root_id) {
this.root_type = types.user_drive;
} else {
this.root_type = types.share_drive;
}
}
async get_single_file(path) {
if (typeof this.files[path] == 'undefined') {
this.files[path] = await this.get_single_file_api(path);
}
return this.files[path];
}
async get_single_file_api(path) {
let arr = path.split('/');
let name = arr.pop();
name = decodeURIComponent(name).replace(/\'/g, "\\'");
let dir = arr.join('/') + '/';
console.log("try " + name, dir);
let parent = await this.findPathId(dir);
console.log("try " + parent)
let url = 'https://www.googleapis.com/drive/v3/files';
let params = {
'includeItemsFromAllDrives': true,
'supportsAllDrives': true
};
params.q = `'${parent}' in parents and name = '${name}' and trashed = false and mimeType != 'application/vnd.google-apps.shortcut'`;
params.fields = "files(id, name, mimeType, size ,createdTime, modifiedTime, iconLink, thumbnailLink, driveId, fileExtension)";
url += '?' + enQuery(params);
let requestOption = await this.requestOptions();
let response;
for (let i = 0; i < 3; i++) {
response = await fetch(url, requestOption);
if (response.ok) {
break;
}
await sleep(800 * (i + 1));
}
let obj = await response.json();
// console.log(obj);
return obj.files[0];
}
async request_list_of_files(path, page_token = null, page_index = 0) {
if (this.path_children_cache == undefined) {
// { :[ {nextPageToken:'',data:{}}, {nextPageToken:'',data:{}} ...], ...}
this.path_children_cache = {};
}
if (this.path_children_cache[path] &&
this.path_children_cache[path][page_index] &&
this.path_children_cache[path][page_index].data
) {
let child_obj = this.path_children_cache[path][page_index];
return {
nextPageToken: child_obj.nextPageToken || null,
curPageIndex: page_index,
data: child_obj.data
};
}
let id = await this.findPathId(path);
let result = await this._list_gdrive_files(id, page_token, page_index);
let data = result.data;
if (result.nextPageToken && data.files) {
if (!Array.isArray(this.path_children_cache[path])) {
this.path_children_cache[path] = []
}
this.path_children_cache[path][Number(result.curPageIndex)] = {
nextPageToken: result.nextPageToken,
data: data
};
}
return result
}
// listing files usign google drive api
async _list_gdrive_files(parent, page_token = null, page_index = 0) {
if (parent == undefined) {
return null;
}
let obj;
let params = {
'includeItemsFromAllDrives': true,
'supportsAllDrives': true
};
params.q = `'${parent}' in parents and trashed = false AND name !='.password' and mimeType != 'application/vnd.google-apps.shortcut' and mimeType != 'application/vnd.google-apps.document' and mimeType != 'application/vnd.google-apps.spreadsheet' and mimeType != 'application/vnd.google-apps.form' and mimeType != 'application/vnd.google-apps.site'`;
params.orderBy = 'folder, name, modifiedTime desc';
params.fields = "nextPageToken, files(id, name, mimeType, size, modifiedTime, driveId, kind, fileExtension)";
params.pageSize = this.authConfig.files_list_page_size;
if (page_token) {
params.pageToken = page_token;
}
let url = 'https://www.googleapis.com/drive/v3/files';
url += '?' + enQuery(params);
let requestOption = await this.requestOptions();
let response;
for (let i = 0; i < 3; i++) {
response = await fetch(url, requestOption);
if (response.ok) {
break;
}
await sleep(800 * (i + 1));
}
obj = await response.json();
return {
nextPageToken: obj.nextPageToken || null,
curPageIndex: page_index,
data: obj
};
}
async password(path) {
if (this.passwords[path] !== undefined) {
return this.passwords[path];
}
let file = await this.get_single_file(path + '.password');
if (file == undefined) {
this.passwords[path] = null;
} else {
let url = `https://www.googleapis.com/drive/v3/files/${file.id}?alt=media`;
let requestOption = await this.requestOptions();
let response = await this.fetch200(url, requestOption);
this.passwords[path] = await response.text();
}
return this.passwords[path];
}
async searchFilesinDrive(origin_keyword, page_token = null, page_index = 0) {
const types = DriveFixedTerms.gd_root_type;
const is_user_drive = this.root_type === types.user_drive;
const is_share_drive = this.root_type === types.share_drive;
const empty_result = {
nextPageToken: null,
curPageIndex: page_index,
data: null
};
if (!is_user_drive && !is_share_drive) {
return empty_result;
}
let keyword = SearchFunction.formatSearchKeyword(origin_keyword);
if (!keyword) {
return empty_result;
}
let words = keyword.split(/\s+/);
let name_search_str = `name contains '${words.join("' AND name contains '")}'`;
let params = {};
if (is_user_drive) {
if (authConfig.search_all_drives) {
params.corpora = 'allDrives';
params.includeItemsFromAllDrives = true;
params.supportsAllDrives = true;
} else {
params.corpora = 'user';
}
}
if (is_share_drive) {
if (authConfig.search_all_drives) {
params.corpora = 'allDrives';
} else {
params.corpora = 'drive';
params.driveId = this.root.id;
}
params.includeItemsFromAllDrives = true;
params.supportsAllDrives = true;
}
if (page_token) {
params.pageToken = page_token;
}
params.q = `trashed = false AND mimeType != 'application/vnd.google-apps.shortcut' and mimeType != 'application/vnd.google-apps.document' and mimeType != 'application/vnd.google-apps.spreadsheet' and mimeType != 'application/vnd.google-apps.form' and mimeType != 'application/vnd.google-apps.site' AND name !='.password' AND (${name_search_str})`;
params.fields = "nextPageToken, files(id, driveId, name, mimeType, size , modifiedTime)";
params.pageSize = this.authConfig.search_result_list_page_size;
params.orderBy = 'folder, name, modifiedTime desc';
let url = 'https://www.googleapis.com/drive/v3/files';
url += '?' + enQuery(params);
let requestOption = await this.requestOptions();
let response;
for (let i = 0; i < 3; i++) {
response = await fetch(url, requestOption);
if (response.ok) {
break;
}
await sleep(800 * (i + 1));
}
let res_obj = await response.json();
return {
nextPageToken: res_obj.nextPageToken || null,
curPageIndex: page_index,
data: res_obj
};
}
async findParentFilesRecursion(child_id, drive_index_no, contain_myself = true) {
const gd = this;
const gd_root_id = gd.root.id;
const user_drive_real_root_id = authConfig.user_drive_real_root_id;
const is_user_drive = gd.root_type === DriveFixedTerms.gd_root_type.user_drive;
const target_top_id = is_user_drive ? user_drive_real_root_id : gd_root_id;
const fields = DriveFixedTerms.default_file_fields;
const parent_files = [];
let meet_top = false;
async function addItsFirstParent(file_obj) {
if (!file_obj) return;
if (!file_obj.parents) return null;
if (file_obj.parents.length < 1) return;
let p_ids = file_obj.parents;
if (p_ids && p_ids.length > 0) {
const first_p_id = p_ids[0];
console.log(first_p_id)
if (drive_list.includes(first_p_id)) {
meet_top = true;
drive_index_no = drive_list.indexOf(first_p_id);
return drive_index_no;
}
const p_file_obj = await gd.findItemById(first_p_id);
if (p_file_obj && p_file_obj.id) {
parent_files.push(p_file_obj);
await addItsFirstParent(p_file_obj);
}
}
return drive_index_no;
}
const child_obj = await gd.findItemById(child_id);
if (contain_myself) {
parent_files.push(child_obj);
}
const drive_id = await addItsFirstParent(child_obj);
console.log("parents -- " + JSON.stringify(parent_files))
return meet_top ? [parent_files, drive_index_no] : null;
}
async findPathById(child_id) {
let p_files
let drive_index_no = 0;
try {
[p_files, drive_index_no] = await this.findParentFilesRecursion(child_id);
} catch (error) {
return null;
}
if (!p_files || p_files.length < 1) return '';
let cache = [];
// Cache the path and id of each level found
p_files.forEach((value, idx) => {
const is_folder = idx === 0 ? (p_files[idx].mimeType === DriveFixedTerms.folder_mime_type) : true;
let path = '/' + p_files.slice(idx).map(it => encodeURIComponent(it.name)).reverse().join('/');
if (is_folder) path += '/';
cache.push({
id: p_files[idx].id,
path: path
})
});
return [cache[0].path, drive_index_no];
}
async findItemById(id) {
const is_user_drive = this.root_type === DriveFixedTerms.gd_root_type.user_drive;
let url = `https://www.googleapis.com/drive/v3/files/${id}?fields=${DriveFixedTerms.default_file_fields}${is_user_drive ? '' : '&supportsAllDrives=true'}`;
let requestOption = await this.requestOptions();
let res = await fetch(url, requestOption);
return await res.json()
}
async findPathId(path) {
let c_path = '/';
let c_id = this.paths[c_path];
let arr = path.trim('/').split('/');
for (let name of arr) {
c_path += name + '/';
if (typeof this.paths[c_path] == 'undefined') {
let id = await this._findDirId(c_id, name);
this.paths[c_path] = id;
}
c_id = this.paths[c_path];
if (c_id == undefined || c_id == null) {
break;
}
}
console.log('findPathId: ', path, c_id)
return this.paths[path];
}
async _findDirId(parent, name) {
name = decodeURIComponent(name).replace(/\'/g, "\\'");
if (parent == undefined) {
return null;
}
let url = 'https://www.googleapis.com/drive/v3/files';
let params = {
'includeItemsFromAllDrives': true,
'supportsAllDrives': true
};
params.q = `'${parent}' in parents and mimeType = 'application/vnd.google-apps.folder' and name = '${name}' and trashed = false`;
params.fields = "nextPageToken, files(id, name, mimeType)";
url += '?' + enQuery(params);
let requestOption = await this.requestOptions();
let response;
for (let i = 0; i < 3; i++) {
response = await fetch(url, requestOption);
if (response.ok) {
break;
}
await sleep(800 * (i + 1));
}
let obj = await response.json();
if (obj.files[0] == undefined) {
return null;
}
return obj.files[0].id;
}
/*async getAccessToken() {
console.log("accessToken");
if (this.authConfig.expires == undefined || this.authConfig.expires < Date.now()) {
const obj = await fetchAccessToken();
if (obj.access_token != undefined) {
this.authConfig.accessToken = obj.access_token;
this.authConfig.expires = Date.now() + 3500 * 1000;
}
}
return this.authConfig.accessToken;
}*/
/*async fetchAccessToken() {
console.log("fetchAccessToken");
const url = "https://www.googleapis.com/oauth2/v4/token";
const headers = {
'Content-Type': 'application/x-www-form-urlencoded'
};
var post_data;
if (this.authConfig.service_account && typeof this.authConfig.service_account_json != "undefined") {
const jwttoken = await JSONWebToken.generateGCPToken(this.authConfig.service_account_json);
post_data = {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: jwttoken,
};
} else {
post_data = {
client_id: this.authConfig.client_id,
client_secret: this.authConfig.client_secret,
refresh_token: this.authConfig.refresh_token,
grant_type: "refresh_token",
};
}
let requestOption = {
'method': 'POST',
'headers': headers,
'body': enQuery(post_data)
};
let response;
for (let i = 0; i < 3; i++) {
response = await fetch(url, requestOption);
if (response.ok) {
break;
}
await sleep(800 * (i + 1));
}
return await response.json();
}*/
async fetch200(url, requestOption) {
let response;
for (let i = 0; i < 3; i++) {
response = await fetch(url, requestOption);
if (response.ok) {
break;
}
await sleep(800 * (i + 1));
}
return response;
}
async requestOptions(headers = {}, method = 'GET') {
const Token = await getAccessToken();
headers['authorization'] = 'Bearer ' + Token;
return {
'method': method,
'headers': headers
};
}
/*sleep(ms) {
return new Promise(function(resolve, reject) {
let i = 0;
setTimeout(function() {
console.log('sleep' + ms);
i++;
if (i >= 2) reject(new Error('i>=2'));
else resolve(i);
}, ms);
})
}*/
}
// end of class googleDrive
const drive = new googleDrive(authConfig, 0);
async function download(id, range = '', inline) {
let url = `https://www.googleapis.com/drive/v3/files/${id}?alt=media`;
const requestOption = await drive.requestOptions();
requestOption.headers['Range'] = range;
let file = await drive.findItemById(id);
if (!file.name) {
return new Response(`{"error":"Unable to Find this File, Try Again."}`, {
status: 500,
headers: {
"content-type": "application/json",
"Access-Control-Allow-Origin": authConfig.cors_domain,
"Cache-Control": "max-age=3600",
}
});
}
let res;
for (let i = 0; i < 3; i++) {
res = await fetch(url, requestOption);
if (res.ok) {
break;
}
sleep(800 * (i + 1));
console.log(res);
}
const second_domain_for_dl = `${uiConfig.second_domain_for_dl}`
if (second_domain_for_dl == 'true') {
const res = await fetch(`${uiConfig.jsdelivr_cdn_src}@${uiConfig.version}/assets/disable_download.html`);
return new Response(await res.text(), {
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
} else if (res.ok) {
const {
headers
} = res = new Response(res.body, res)
headers.set("Content-Disposition", `attachment; filename="${file.name}"`);
headers.set("Content-Length", file.size);
authConfig.enable_cors_file_down && headers.append('Access-Control-Allow-Origin', '*');
inline === true && headers.set('Content-Disposition', 'inline');
return res;
} else if (res.status == 404) {
return new Response(not_found, {
status: 404,
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
} else if (res.status == 403) {
const details = await res.text()
return new Response(details, {
status: 403,
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
} else {
const details = await res.text()
/*const res = await fetch(`${uiConfig.jsdelivr_cdn_src}@${uiConfig.version}/assets/download_error.html`);
return new Response(await res.text(), {
headers: {
"content-type": "text/html;charset=UTF-8",
},
})*/
return new Response(details, {})
}
}
String.prototype.trim = function(char) {
if (char) {
return this.replace(new RegExp('^\\' + char + '+|\\' + char + '+$', 'g'), '');
}
return this.replace(/^\s+|\s+$/g, '');
};
function decodeJwtToken(token) {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request, event).catch(
(err) => new Response("Report this page when asked at the time of support... ==> " + err.stack, { status: 500 })
)
);
});